/* A simple javascript websockets client for the rosbridge v2.0 protocol. 
 * 
 * This file contains the AMD-compatible version of ros.js.  For a standalone version,
 * use ros.js
 * 
 * **/

define("rosjs", [], function() {
    var Bridge = function(url) {
        // Initialize internal variables
        this.service_handlers = {};
        this.service_seed = 0;
        this.subscription_handlers = {};

        // Ensure that JSON and WebSocket are available. Thrown an
        // exception if not
        if (!WebSocket && MozWebSocket) {
            WebSocket = MozWebSocket;
        }
        if (!WebSocket) {
            throw "Browser does not support WebSockets";
        }
        if (!JSON) {
            throw "Browser does not support JSON";
        }

        // Create the connection
        this.socket = new WebSocket(url);

        var self = this
        this.socket.onmessage = function() {
            self.receiveMessage.apply(self, arguments);
            self.onMessage.apply(self, arguments);
        }
        this.socket.onerror = function() {
            self.onError.apply(self, arguments);
        }
        this.socket.onopen = function() {
            self.onOpen.apply(self, arguments);
        }
        this.socket.onclose = function() {
            self.onClose.apply(self, arguments);
        }
    }

    Bridge.prototype.send = function(op, id, msg) {
        msg.op = op;
        if (id != null) {
            msg.id = id;
        }
        this.socket.send(JSON.stringify(msg));
    }

    Bridge.prototype.advertise = function(topic, type, /* optional */id) {
        this.send("advertise", id, {
            topic : topic,
            type : type
        });
    }

    Bridge.prototype.unadvertise = function(topic, /* optional */id) {
        this.send("unadvertise", id, {
            topic : topic
        });
    }

    Bridge.prototype.publish = function(topic, msg, /* optional */id) {
        this.send("publish", id, {
            topic : topic,
            msg : msg
        });
    }

    Bridge.prototype.subscribe = function(callback, topic, /* optional */
            type, /* optional */throttle_rate,
            /* optional */queue_length, /* optional */fragment_size, /* optional */
            compression, /* optional */id) {
        // Construct the message
        msg = {
            topic : topic
        };
        if (type != null)
            msg.type = type;
        if (throttle_rate != null)
            msg.throttle_rate = throttle_rate;
        if (queue_length != null)
            msg.queue_length = queue_length;
        if (fragment_size != null)
            msg.fragment_size = fragment_size;
        if (compression != null)
            msg.compression = compression;

        // Send the message
        this.send("subscribe", id, msg);

        // Save the callback
        if (this.subscription_handlers[topic] == null) {
            this.subscription_handlers[topic] = {};
        }
        if (this.subscription_handlers[topic][id] == null) {
            this.subscription_handlers[topic][id] = [];
        }
        this.subscription_handlers[topic][id].push(callback);
    }

    Bridge.prototype.unsubscribe = function(topic, /* optional */id) {
        // Send the message
        this.send("unsubscribe", id, {
            topic : topic
        });

        // Delete callbacks
        if (this.subscription_handlers[topic]
                && this.subscription_handlers[topic][id]) {
            delete this.subscription_handlers[topic][id];
        }
        if (id == null
                || Object.keys(this.subscription_handlers[topic]).length == 0) {
            delete this.subscription_handlers[topic];
        }
    }

    Bridge.prototype.callService = function(callback, service, /* optional */
            args,
            /* optional */fragment_size, /* optional */compression, /* optional */
            id) {
        // Construct the message
        msg = {
            service : service
        };
        if (args != null)
            msg.args = args;
        if (fragment_size != null)
            msg.fragment_size = fragment_size;
        if (compression != null)
            msg.compression = compression;

        // Generate an ID for service calls
        if (id == null) {
            id = this.service_seed;
            this.service_seed++;
        }

        // Send the message
        this.send("call_service", id, msg);

        // Save the callback
        if (this.service_handlers[service] == null) {
            this.service_handlers[service] = {};
        }
        this.service_handlers[service][id] = callback;
    }

    Bridge.prototype.receiveMessage = function(event) {
        msg = JSON.parse(event.data);

        switch (msg.op) {
        case "publish":
            this.onPublish(msg);
            break;
        case "service_response":
            this.onServiceResponse(msg);
            break;
        }
    }

    Bridge.prototype.onOpen = function(event) {
    }
    Bridge.prototype.onClose = function(event) {
    }
    Bridge.prototype.onError = function(event) {
    }
    Bridge.prototype.onMessage = function(event) {
    }

    Bridge.prototype.onPublish = function(message) {
        // Extract message details
        topic = message.topic;
        msg = message.msg;

        // Copy the callbacks - in case the callback modifies the subscription
        var callbacks = [];
        for ( var id in this.subscription_handlers[topic]) {
            callbacks = callbacks
                    .concat(this.subscription_handlers[topic][id]);
        }

        // Call all the callbacks
        for ( var i = 0; i < callbacks.length; i++) {
            try {
                callbacks[i](msg);
            } catch (err) {
                // Best we can do is print the error
                console.error(err);
            }
        }
    }

    Bridge.prototype.onServiceResponse = function(response) {
        // Extract message details
        service = response.service;
        values = response.values;
        id = response.id;

        // Call the callback and remove it
        if (this.service_handlers[service]
                && this.service_handlers[service][id]) {
            callback = this.service_handlers[service][id];
            delete this.service_handlers[service][id];
            if (Object.keys(this.service_handlers[service]).length == 0) {
                delete this.service_handlers[service];
            }
            callback(values);
        }
    }
    
    return Bridge;
})